Building A System

The PyBigDFT package allows you to setup a system of interest, perform BigDFT calculations, and post-process those results all from a high level python scripts. To get started with PyBigDFT, let’s begin with how to build a system.

In PyBigDFT, systems are built in three layers. On the lowest layer are Atoms. Atoms inherit from dictionaries, and can be manipulated as such so that you can store any needed. Collections of Atoms are stored as Fragments. Fragments inherit from lists, allowing you to combine and slice them. Finally, at the top layer we have a system, which is a named collection of Fragments. Like Atoms, Systems inherit from dictionaries, so that you can manipulate them as key value pairs.

Atoms

First we begin with atoms. To describe an atom, you should define an atomic symbol, and a position.

[1]:
from BigDFT.Atoms import Atom
at = Atom({'r': [1.0, 0.0, 0.0], 'sym': "He", 'units': 'bohr'})
print(dict(at))
at["source"] = "tutorial"
print(dict(at))
{'r': [1.0, 0.0, 0.0], 'sym': 'He', 'units': 'bohr'}
{'r': [1.0, 0.0, 0.0], 'sym': 'He', 'units': 'bohr', 'source': 'tutorial'}

There are helper routines to let you access these dictionary elements.

[2]:
print(at.get_position())
print(at.get_position(units="angstroem"))
[1.0, 0.0, 0.0]
[0.52917721092, 0.0, 0.0]
[3]:
at.set_position([1.0, 1.0, 1.0], units="angstroem")
print(at.get_position(units="angstroem"))
[1.0, 1.0, 1.0]
[4]:
print(at.atomic_number)
2

By default, the atoms in PyBigDFT don’t know how many electrons they have. This is because the pseudopotential approximation means the number of electrons used could depend on our choice of pseudopotential. However, we can set this ourself if we’d like.

[5]:
at.nel = 2
print(at.nel)
2

Fragments

Now we look at fragments, which are collections of atoms. Fragments might be built by simply appending atoms to an empty Fragment.

[6]:
at1 = Atom({'r': [1.0, 0.0, 0.0], 'sym': "He", 'units': 'bohr', "nzion": 2})
at2 = Atom({'r': [3.0, 0.0, 0.0], 'sym': "He", 'units': 'bohr', "nzion": 2})
[7]:
from BigDFT.Fragments import Fragment
frag = Fragment()
frag.append(at1)
frag.append(at2)

for at in frag:
    print(dict(at))
{'r': [1.0, 0.0, 0.0], 'sym': 'He', 'units': 'bohr', 'nzion': 2}
{'r': [3.0, 0.0, 0.0], 'sym': 'He', 'units': 'bohr', 'nzion': 2}

There are also some helper routines associated with fragments.

[8]:
print(frag.centroid)
print(frag.nel)
[2. 0. 0.]
4
[9]:
from copy import deepcopy
frag2 = deepcopy(frag)
frag2.translate(vec=[0.0, 5.0, 0.0])
[10]:
from BigDFT.Fragments import distance
print(distance(frag, frag2))
5.0

Systems

Systems are named collections of fragments. In general, we use a naming convention for systems that goes “NAME:ID” where name is a string and ID is a number. This can help with maintaining ordering, or distinguishing between the types of fragments.

[11]:
from BigDFT.Systems import System
sys = System()
sys["FRA:1"] = frag
sys["FRA:2"] = frag2

Now that we’ve reached the top level, we can begin interacting with other parts of the code. For example, the Visualization module.

[12]:
from BigDFT.Visualization import InlineVisualizer
viz = InlineVisualizer(500,400)

viz.display_system(sys)

You appear to be running in JupyterLab (or JavaScript failed to load for some other reason). You need to install the 3dmol extension:
jupyter labextension install jupyterlab_3dmol

Notice how the code was able to color the different fragments using this representation. The System module offers helper routines for dealing with the stored fragments.

[13]:
print(sys.get_nearest_fragment("FRA:1"))
print(sys.centroid)
FRA:2
[2.  2.5 0. ]

To access again the atoms in a system, try the following double loop pattern:

[14]:
for fragid, frag in sys.items():
    print(fragid)
    for at in frag:
        print(dict(at))
FRA:1
{'r': [1.0, 0.0, 0.0], 'sym': 'He', 'units': 'bohr', 'nzion': 2}
{'r': [3.0, 0.0, 0.0], 'sym': 'He', 'units': 'bohr', 'nzion': 2}
FRA:2
{'r': [1.0, 5.0, 0.0], 'sym': 'He', 'units': 'bohr', 'nzion': 2}
{'r': [3.0, 5.0, 0.0], 'sym': 'He', 'units': 'bohr', 'nzion': 2}

Solid State Systems

A system has a unit cell associated with it that you can use to define solid state systems. The UnitCell class is available to manage the cell.

[15]:
from BigDFT.UnitCells import UnitCell
sys.cell = UnitCell([5, 5, 5], units="bohr")
[16]:
print(sys.cell.get_posinp())
[5, 5, 5]

BigDFT is able to handle several boundary conditions. If the cell is None, it will be a free boundary. If all three values are set, you get a periodic system. For a wire boundary condition, set the y and x values to infinity.

[17]:
sys.cell = UnitCell([float("inf"), float("inf"), 5], units="angstroem")
print(sys.cell.get_posinp())
[inf, inf, 9.44863062282531]

For the surface condition, set just the Y value.

[18]:
sys.cell = UnitCell([5, float("inf"), 5], units="bohr")
print(sys.cell.get_posinp())
[5, inf, 5]

Conclusion

In this tutorial, we presented the three layers of system representation in PyBigDFT: Atoms, Fragments, and Systems. By looking at the rest of the documentation, you can discover advanced features of these representations, and how to connect them to the rest of the code.